package com.kaibes.object;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.kaibes.object.annotation.BesResource;
import com.kaibes.object.code.BesClassParser;

public class BesObjectContext {

	private static BesObjectContext instance;

	private Map<String, ObjectPlan> namePlanMap = new HashMap<>();
	private Map<String, ObjectPlan> typePlanMap = new HashMap<>();

	public static void run(Class<?> clazz) {
		run(clazz.getPackage().getName());

	}

	public static void run(String packgeName) {
		instance = new BesObjectContext();
		List<Class<?>> classList = BesClassParser.getMXClass(packgeName);
		for (Class<?> clazzC : classList) {
			try {
				Object obj = clazzC.newInstance();
				List<Method> methods = BesClassParser.getMXMethod(clazzC);
				for (Method method : methods) {
					instance.addPlan(obj, method);
				}
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}

	}

	@SuppressWarnings("unchecked")
	public static <T> T getValue(String name) {
		ObjectPlan plan = instance.namePlanMap.get(name);
		if (plan != null) {
			return (T) plan.getValue();
		}
		return null;
	}

	public static void clearValue(String name) {
		if (instance.namePlanMap.containsKey(name)) {
			instance.namePlanMap.get(name).value = null;
		}
	}

	public static Object getValue(Class<?> type) {
		ObjectPlan plan = instance.typePlanMap.get(type.getName());
		if (plan == null) {
			plan = instance.addPlan(type);
		}
		return plan.value;
	}

	public static void clearValue(Class<?> clazz) {
		if (instance.typePlanMap.containsKey(clazz.getName())) {
			instance.typePlanMap.remove(clazz.getName());
		}
	}

	public static void initValue(Object obj) {
		List<Field> fields = BesClassParser.getMXField(obj.getClass());
		for (Field field : fields) {
			BesResource resource = field.getDeclaredAnnotation(BesResource.class);
			String name = field.getName();
			if (!resource.value().isEmpty()) {
				name = resource.value();
			}

			try {
				boolean accessible = field.isAccessible();
				field.setAccessible(true);
				Object value = getValue(name);
				if (value == null) {
					value = getValue(field.getType());
				}
				field.set(obj, value);
				field.setAccessible(accessible);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

	private ObjectPlan addPlan(Class<?> type) {
		ObjectPlan plan = null;
		try {
			Object value = type.newInstance();
			plan = new ObjectPlan();
			plan.value = value;
			typePlanMap.put(type.getName(), plan);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return plan;
	}

	private void addPlan(Object obj, Method method) {
		ObjectPlan plan = new ObjectPlan();
		plan.obj = obj;
		plan.createMethod = method;

		BesResource resource = method.getDeclaredAnnotation(BesResource.class);
		String name = method.getName();
		if (!resource.value().isEmpty()) {
			name = resource.value();
		}
		namePlanMap.put(name, plan);
	}

	private class ObjectPlan {
		private Object obj;
		private Method createMethod;
		private Object value;

		private Object getValue() {
			if (value == null) {
				try {
					Parameter[] ps = createMethod.getParameters();
					Object[] objs = new Object[ps.length];
					for (int i = 0; i < ps.length; i++) {
						Parameter parameter = ps[i];
						String name = parameter.getName();
						if (parameter.isAnnotationPresent(BesResource.class)) {
							BesResource resource = parameter.getDeclaredAnnotation(BesResource.class);
							String value = resource.value();
							if (!value.isEmpty()) {
								name = value;
							}
						}
						Object obj = BesObjectContext.getValue(name);
						if (obj == null) {
							obj = BesObjectContext.getValue(parameter.getType());
						}
						objs[i] = obj;
					}
					value = createMethod.invoke(obj, objs);
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
			return value;
		}
	}
}
